#include "stdafx.h"
#include <io.h>
#include <WS2tcpip.h>
#include <MSWSock.h>
#include <fstream>
#include <io.h>
#include <sstream>
#include <codecvt>
#include "HTTPServer.h"
#include "CodeByUTF8.h"
#include "HTTPRequest.h"
#include "HTTPCloseEnum.h"
#include "HTTPResponser.h"

//允许打开的文件数
#define MAX_WINIO 16384


HTTPServer::HTTPServer():portNumber(0),
	ipAddress(""),
	tempFileNameAddress(""),
	docmentRoot("K:\\wwwroot\\"),
	defaultFileNamesList("index.html"),
	tmpFileNameNo(0),
	listenStock(IOCP_NULLKEY),
	socketNew(INVALID_SOCKET),
	hrt(true)
{
	/*
	* 额外的16个字节是 AcceptEx()的要求,见MSDN.
	*/
	this->acceptEvent.length = (sizeof(sockaddr_in) + 16) * 2;

	this->acceptEvent.buf = new unsigned char[this->acceptEvent.length];
}


HTTPServer::~HTTPServer()
{
	delete [] this->acceptEvent.buf;
}

int HTTPServer::onRequestDataReceived(AbstractIRequest * request, size_t bytesTransfered)
{
	return 0;
}

int HTTPServer::onResponderDataSent(AbstractIResonser * responder, size_t bytesTransfered)
{
	return 0;
}

void HTTPServer::onRequest(AbstractIRequest * request, int status)
{
	ConnectDefinition *conn = reinterpret_cast<ConnectDefinition*>(request->getConnectionId());

	if (status != RE_SUCCESS)
	{
		this->doConnectionClosed(conn, status);

		//关闭连接
		return;
	}

	assert(conn->responserClient == NULL);

	conn->responserClient = new HTTPResponser(this, &this->iocNetWork);

	int code = conn->responserClient->run(conn, conn->clentSocket, conn->requestClient);

	if (code != RE_SUCCESS)
	{
		this->doConnectionClosed(conn, status);

		return;
	}

}

// 请求完成
void HTTPServer::onResponder(AbstractIResonser * responder, int status)
{
	ConnectDefinition * conn = reinterpret_cast<ConnectDefinition *>(responder->getConnectionId());

	this->doRequestComplete(conn, status);
}

/*映射服务器文件路径*/
bool HTTPServer::mapServerFilePath(const std::string & url, std::string & serverPath)
{
	if ((this->docmentRoot).size() == 0)
	{
		return false;
	}

	serverPath = this->docmentRoot;

	char temp = serverPath.back();

	if (temp == '\\')
	{
		serverPath.erase(--serverPath.end());
	}

	size_t pos = url.find('?');

	if (std::string::npos != pos)
	{
		//根目录
		serverPath = serverPath.append(url.substr(0, pos));
	}
	else
	{
		CodeByUTF8 * utf8Obj = CodeByUTF8::getInstance(url);

		std::string urlByUTF8 = utf8Obj->decodeURL();

		serverPath = serverPath.append(urlByUTF8);

		utf8Obj->destory();
	}

	

	// 如果是目录名并且不允许浏览目录,则尝试添加默认文件名
	if (temp == '\\' && !this->isOpenDirectoryVisible)
	{
		// 禁止浏览目录,先尝试打开默认文件
		bool hasDftFile = false;

		std::string defaultFileName(this->defaultFileNamesList.append(","));

		std::string  temp;

		while ((pos = defaultFileName.find(',')) != std::string::npos)
		{
			if (pos == 0)
			{
				break;
			}

			temp = defaultFileName.substr(0, pos);

			std::string serverPathCopy(serverPath);
			// 指针所指向的值不能变
			const std::string * defaultFilePath = & serverPathCopy.append(temp);

			if (0 == _access(defaultFilePath->c_str(), 0))
			{
				serverPath = serverPath.append(temp);

				hasDftFile = true;

				break;
			}

			defaultFileName = defaultFileName.substr(pos);
		}

		return hasDftFile;
	}

	return true;
}

//获取系统临时文件
std::string HTTPServer::tmpFileName()
{
	char tempFile[MAX_PATH + 1] = {0};

	/*
		//声明:
		GetTempFileName(
		  lpPathName: PChar;     {路径}
		  lpPrefixString: PChar; {前缀}
		  uUnique: UINT;         {指定生成文件名的数字, 文件名将根据参数2、参数3来生成}
		  lpTempFileName: PChar  {文件名需要的缓冲区}
		): UINT;                 {指定生成文件名的数字, 如果参数3是0, 则返回文件名长度}
	*/
	if (0 == GetTempFileNameA(this->tempFileNameAddress.c_str(), this->tmpFileNamePrefix, 0, tempFile))
	{
		/*创建临时文件*/

		assert(0);

		//生成序号
		int number = 0;
		
		//加锁
		this->lockObj.lockUp();
		
		number = ++ this->tmpFileNameNo;

		this->lockObj.unLock();

		std::stringstream fmt;

		//访问最后一个字符，返回对字符串最后一个字符的引用。这个函数不能在空字符串上调用。
		if (this->tempFileNameAddress.back() == '\\')
		{
			fmt << this->tempFileNameAddress << number << ".temp";
		}
		else
		{
			fmt << this->tempFileNameAddress << '\\' << number << ".temp";
		}

		return fmt.str();
	}
	
	return tempFile;
}

const std::string & HTTPServer::docRoot()
{
	// TODO: 在此处插入 return 语句
	return this->docmentRoot;
}

bool HTTPServer::isDirectoryVisible()
{
	return this->isOpenDirectoryVisible;
}

const std::string & HTTPServer::defaultFileNames()
{
	// TODO: 在此处插入 return 语句

	return this->defaultFileNamesList;
}

const std::string & HTTPServer::ip()
{
	// TODO: 在此处插入 return 语句
	return this->ipAddress;
}

unsigned int HTTPServer::port()
{
	return this->portNumber;
}



unsigned int HTTPServer::maxConnectionsPerIp()
{
	return this->maxConnectionsPerIpNumber;
}

unsigned int HTTPServer::maxConnections()
{
	return this->maxConnectionNumber;
}

unsigned int HTTPServer::maxConnectionSpeed()
{
	return this->maxConnectionSpeedNumber;
}

unsigned long HTTPServer::sessionTimeout()
{
	return this->sessionTimeOutNumber;
}

unsigned long HTTPServer::recvTimeout()
{
	return this->recvTimeoutNumber;
}

unsigned long HTTPServer::sendTimeout()
{
	return this->sessionTimeOutNumber;
}

unsigned long HTTPServer::keepAliveTimeout()
{
	return this->keepAliveTimeoutNumber;
}

//启动服务
bool HTTPServer::run(AbstractConf * httpConfig)
{
	do
	{
		if (this->isRun)
		{
			break;
		}
	
		//载入配置
		this->portNumber = httpConfig->getPort();

		this->maxConnectionNumber = httpConfig->getMaxConnect();

		this->maxConnectionsPerIpNumber = httpConfig->getMaxConnectionsPerIpNumber();

		this->ipAddress = httpConfig->getIP();

		this->keepAliveTimeoutNumber = httpConfig->getKeepAliveTimeout();

		this->sendTimeoutNumber = httpConfig->getSendTimeout();

		this->sessionTimeOutNumber = httpConfig->getSessionTimeout();

		this->recvTimeoutNumber = httpConfig->getRecvTimeout();

		this->docmentRoot = httpConfig->getWebPath();

		this->isOpenDirectoryVisible = httpConfig->isDocVisible();

		this->tempFileNameAddress = httpConfig->getTmpRoot();

		//设置允许打开的文件数
		assert(this->maxConnectionNumber <= MAX_WINIO);

		//初始化网络模块
		
		if (0 != this->iocNetWork.init(0))
		{
			//初始化失败
			break;
		}


		//初始化侦听套接字，并注册到IOC网络模块，接收用户请求
		SOCKET socket = this->initializeSocket(this->ipAddress, this->portNumber);

		if (socket == NULL)
		{
			//初始化失败
			break;
		}

		this->listenStock = this->iocNetWork.add(socket, 0, 0, 0);

		//记录状态
		this->isRun = true;

		this->clientMap.clear();

		this->connectionIp.clear();

		//临时文件名生成参数
		this->tmpFileNameNo = 0;
		
		srand(static_cast<unsigned long>(time(NULL)));

		sprintf_s(this->tmpFileNamePrefix, "%d", rand() % 1000);

		//执行一次accept（握手）
		int receive = this->doAccept();

		if (receive != S_SUCCESS)
		{
			return false;
		}

		//成功的出口
		return true;

	} while (0);

	//失败的出口

	return false;
}

SOCKET HTTPServer::initializeSocket(std::string & ipAddress, unsigned int port)
{
	SOCKET hListenSocket = INVALID_SOCKET;

	/*
	这三个参数都是int类型的，但是他们各自有各自的含义，下面我们针对这三个参数进行分析：

	（1）int domin:即协议域，又称为协议族。协议族决定了socket的地址类型，在通信中必须采用对应的地址，如AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL（或称AF_UNIX，Unix域socket）、AF_ROUTE等等。通常情况下我们使用AF_INET。

		AF_INET的定义是：#define AF_INET 2，所以这里的参数都是int型的

	（2）int type:是套接口类型，主要SOCK_STREAM(建立TCP连接)、SOCK_DGRAM（建立UDP）、SOCK_RAW；

		SOCK_STREAM(建立TCP连接)：提供面向连接的可靠的数据传输服务。数据被看作是字节流，无长度限制。例如FTP协议就采用这种。

		SOCK_DGRAM（建立UDP）：提供无连接的数据传输服务，不保证可靠性。

		SOCK_RAW：该接口允许对较低层次协议，如IP，ICMP直接访问。

	（3）int protocol:指定协议。
		常用的协议有，IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等，
		它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。通常情况系设为0。

		注意：并不是上面的type和protocol可以随意组合的，如SOCK_STREAM不可以跟IPPROTO_UDP组合。
		当protocol为0时，会自动选择type类型对应的默认协议。
	*/
	//创建套接字监听对象
	if (INVALID_SOCKET == (hListenSocket = socket(PF_INET, SOCK_STREAM, 0)))
	{

 		return 0;
	}

	
	u_long argp;

	/*
		int PASCAL FAR ioctlsocket( SOCKET s, long cmd, u_long FAR* argp);
			s：一个标识套接口的描述字。
			cmd：对套接口s的操作命令。
			argp：指向cmd命令所带参数的指针。
	 * 本函数可用于任一状态的任一套接口。它用于获取与套接口相关的操作参数，而与具体协议或通讯子系统无关。支持下列命令：
		FIONBIO：
			允许或禁止套接口s的非阻塞模式。argp指向一个无符号长整型，如允许非阻塞模式则非零，如禁止非阻塞模式则为零。
			当创建一个套接口时，它就处于阻塞模式（也就是说非阻塞模式被禁止）。这与BSD套接口是一致的。
			WSAAsynSelect()函数将套接口自动设置为非阻塞模式。
			如果已对一个套接口进行了WSAAsynSelect() 操作，
			则任何用ioctlsocket()来把套接口]重新设置成阻塞模式的试图将以WSAEINVAL失败。
			为了把套接口重新设置成阻塞模式，应用程序必须首先用WSAAsynSelect()调用（IEvent参数置为0）来禁止WSAAsynSelect()。
		FIONREAD：
			确定套接口s自动读入的数据量。argp指向一个无符号长整型，其中存有ioctlsocket()的返回值。
			如果s是SOCKET_STREAM类型，则FIONREAD返回在一次recv()中所接收的所有数据量。
			这通常与套接口中排队的数据总量相同。如果S是SOCK_DGRAM 型，则FIONREAD返回套接口上排队的第一个数据报大小。
		SIOCATMARK：
			确实是否所有的带外数据都已被读入。这个命令仅适用于SOCK_STREAM类型的套接口，且该套接口已被设置为可以在线接收带外数据（SO_OOBINLINE）。
			如无带外数据等待读入，则该操作返回TRUE真。否则的话返回FALSE假，下一个recv()或recvfrom()操作将检索“标记”前一些或所有数据。
			应用程序可用SIOCATMARK操作来确定是否有数据剩下。
			如果在“紧急”（带外）数据[前有常规数据，则按序接收这些数据（请注意，recv()和recvfrom()操作不会在一次调用中混淆常规数据与带外数]据）。
			argp指向一个BOOL型数，ioctlsocket()在其中存入返回值。
	 */
	ioctlsocket(hListenSocket, FIONBIO, &argp);

	sockaddr_in addr;

	addr.sin_family = AF_INET;

	/*
		htons是将整型变量从主机字节顺序转变成网络字节顺序， 就是整数在地址空间存储方式变为高位字节存放在内存的低地址处。
		网络字节顺序是TCP/IP中规定好的一种数据表示格式，它与具体的CPU类型、操作系统等无关，
		从而可以保证数据在不同主机之间传输时能够被正确解释，网络字节顺序采用big-endian排序方式。
	*/
	addr.sin_port = htons(port);

	if (ipAddress == "")
	{
		addr.sin_addr.s_addr = htons(INADDR_ANY);
	}
	else
	{
		//inet_addr是一个计算机函数，功能是将一个点分十进制的IP转换成一个长整数型数（u_long类型）等同于inet_addr()。
		addr.sin_addr.s_addr = inet_pton(AF_INET, ipAddress.c_str(), &addr.sin_addr);
	}

	if (0 != bind(hListenSocket, (sockaddr *)&addr, sizeof(sockaddr_in)))
	{
		//无法绑定端口
		return 0;
	}

	/*
		参数backlog

		这个参数涉及到一些网络的细节。进程处理一个一个连接请求的时候，可能还存在其它的连接请求。
		因为TCP连接是一个过程，所以可能存在一种半连接的状态，有时由于同时尝试连接的用户过多，
		使得服务器进程无法快速地完成连接请求。如果这个情况出现了，服务器进程希望内核如何处理呢？
		内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接，
		这样的一个队列内核不可能让其任意大，所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。

		毫无疑问，服务器进程不能随便指定一个数值，内核有一个许可的范围。这个范围是实现相关的。很难有某种统一，一般这个值会小30以内。
	*/
	if (0 != listen(hListenSocket, 10))
	{
		//侦听失败
		return 0;
	}
	return hListenSocket;
}

// 执行一次握手
int HTTPServer::doAccept()
{
	// 检查握手属性是否为空
	
	assert(this->listenStock != IOCP_NULLKEY);

	// 创建一个新的套接字

	/*
		创建一个与指定传送服务提供者捆绑的套接口，可选地创建和/或加入一个套接口组。
			此函数为socket（）的扩展版本，其功能都是创建一个原始套接字。使用时需要包含
			winsock2.h 头文件和链接ws2_32.lib库。
		SOCKET WSASocket (
　　		  int af,
　　		  int type,
　　		  int protocol,
　　		  LPWSAPROTOCOL_INFO lpProtocolInfo,
　　		  GROUP g,
　　		  DWORD dwFlags
　　		);
		参数说明：
		af：[in]一个地址族规范。目前仅支持AF_INET格式，亦即ARPA Internet地址格式。
		type：新套接口的类型描述。
		protocol：套接口使用的特定协议，如果调用者不愿指定协议则定为0。
		lpProtocolInfo：一个指向PROTOCOL_INFO结构的指针，该结构定义所创建套接口的特性。如果本参数非零，则前三个参数（af, type, protocol）被忽略。
		g：保留给未来使用的套接字组。套接口组的标识符。
		iFlags：套接口属性描述。
		若无错误发生，WSASocket()返回新套接口的描述字。
		否则的话，返回 INVALID_SOCKET，应用程序可定调用WSAGetLastError()来获取相应的错误代码。
		sock_stream 是有保障的(即能保证数据正确传送到对方)面向连接的SOCKET，多用于资料(如文件)传送。
		原始套接字(SOCK_RAW)是一种不同于SOCK_STREAM、SOCK_DGRAM的套接字，它实现于系统核心。
		然而，原始套接字能做什么呢？
		首先来说，普通的套接字无法处理ICMP、IGMP等网络报文，而SOCK_RAW可以；
		其次，SOCK_RAW也可以处理特殊的IPv4报文；
		此外，利用原始套接字，可以通过IP_HDRINCL套接字选项由用户构造IP头。
		总体来说，SOCK_RAW可以处理普通的网络报文之外，还可以处理一些特殊协议报文以及操作IP层及其以上的数据。
	*/
	this->socketNew = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

	if (INVALID_SOCKET == this->socketNew)
	{
		//失败
		return 0;
	}

	// 调用 AcceptEx 函数

	int receive = this->iocNetWork.accept(this->listenStock, this->socketNew, this->acceptEvent.buf, this->acceptEvent.length, 0, IOCPCallback, this);

	if (IOCP_PENDING != receive)
	{

		//握手失败
		return S_NETWORK_FAILD;
	}

	return S_SUCCESS;
}

HTTPServer::ConnectDefinition * HTTPServer::allocConnectionByCient(char* ip, unsigned int port)
{
	ConnectDefinition *conn = new ConnectDefinition();

	if (conn == NULL)
	{
		return NULL;
	}

	memset(conn, 0, sizeof(ConnectDefinition));


	strcpy_s(conn->clientIp, 50, ip);

	conn->port = port;

	conn->requestClient = new HTTPRequest(this, &(this->iocNetWork));

	conn->startTime = this->hrt.getNow();

	return conn;
}

/*
 NO.1 //IOCP 上下文结构
 NO.2 读写保护
 NO.3 是否失败重试
 NO.4 发送的数据长度
 NO.5 缓冲区
 NO.6 缓冲区长度
 NO.7 当前类对象
*/
void __stdcall HTTPServer::IOCPCallback(void * s, int flags, bool result, int transfered, byte * buf, size_t len, void * param)
{
	HTTPServer *server = reinterpret_cast<HTTPServer*>(param);

	assert(flags & IOCP_ACCEPT);

	server->onAccept(result);

}

/*
  处理网络事件
*/
void HTTPServer::onAccept(bool success)
{

	if (!success)
	{
		// 停止客户端的连接

		shutdown(this->socketNew, SD_BOTH);

		closesocket(this->socketNew);

		this->socketNew = INVALID_SOCKET;

		this->doAccept();

		return;
	}

	// 获取 socket 信息
	SOCKET socketData = this->iocNetWork.getSockopt(this->listenStock);

	// 更新连接socket（客户端）
	/*
		sockfd：标识一个套接口的描述字。
		level：选项定义的层次；支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
		optname：需设置的选项。
		optval：指针，指向存放选项待设置的新值的缓冲区。
		optlen：optval缓冲区长度。
	*/
	if (0 != setsockopt(this->socketNew, 
		SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char *)&socketData, sizeof(socketData)))
	{
		AfxMessageBox(WSAGetLastError());
	}

	//获取客户端信息
	sockaddr_in addr;

	int nameLen = (sizeof(sockaddr_in));

	// 获取socket的对方地址 getpeername
	if (0 != getpeername(this->socketNew, (sockaddr*)&addr, &nameLen))
	{
		AfxMessageBox(WSAGetLastError());
	}

	//获取客户端端口
	USHORT port = addr.sin_port;

	char ip[16];
	// 将一个十进制网络字节序转换为点分十进制IP格式的字符串。
	inet_ntop(addr.sin_family, &addr.sin_addr, ip, sizeof(ip));

	//创建链接对象
	SOCKET linkObj = this->socketNew;

	this->socketNew = INVALID_SOCKET;

	//连接
	ConnectDefinition *conn = nullptr;

	//上锁
	this->lockObj.lockUp();

	bool connRefuse = false;

	bool kicked = false;

	do
	{
		// 检查 连接对象是否超过预先设置

		// 检查连接数
		if (this->maxConnections() < this->clientMap.size())
		{
			connRefuse = true;

			AfxMessageBox("超过最大连接数");
			break;
		}

		// 检查ip连接数
		std::map<std::string, unsigned int>::iterator iteratorClientIp = this->connectionIp.end();
		
		unsigned int ipCount = this->maxConnectionsPerIp();
		
		if (ipCount > 0)
		{
			iteratorClientIp = this->connectionIp.find(ip);

			if (iteratorClientIp != this->connectionIp.end() && iteratorClientIp->second >= ipCount)
			{
				//剔除
				kicked = true;

				AfxMessageBox("超过最大连接数");
				
				break;
			}
		}

		//为新用户分配资源
		conn = this->allocConnectionByCient(ip, port);

		conn->clentSocket = this->iocNetWork.add(linkObj, this->sessionTimeout(), 0, this->maxConnectionSpeed());

		this->clientMap.insert(std::pair<void *, ConnectDefinition*>(conn->clentSocket, conn));

		// 记录连接数量
		if (ipCount > 0)
		{

			if (iteratorClientIp != this->connectionIp.end())
			{
				iteratorClientIp->second++;
			}
			else
			{
				connectionIp.insert(std::pair<std::string, unsigned int>(ip, port));
			}
		}

	} while (false);

	this->lockObj.unLock();

	//接收HTTP请求

	if (conn != NULL)
	{
		assert(conn->requestClient);

		int receive = conn->requestClient->run(conn, conn->clentSocket, this->recvTimeoutNumber);

		if (RE_SUCCESS != receive)
		{
			// 请求不成功
			// 关闭请求
			
		}
		else
		{
			// 接收成功 由于是网络事件驱动的模型, 
			// conn->request->recv() 成功后, 不保证 conn 对象的有效性.
		}
	}
	else
	{
		// 记录日志
		if (connRefuse)
		{
			//拒绝连接
		}

		if (kicked)
		{
			//剔除
		}

		shutdown(linkObj, SD_BOTH);

		closesocket(linkObj);
	}

	//准备下一个连接
	this->doAccept();
}

/*关闭连接 可能是成功的时候关闭，也可能是失败的时候关闭*/
void HTTPServer::doConnectionClosed(ConnectDefinition * conn, int code)
{
	// TODO

	//停止网络调用
	this->iocNetWork.remove(conn->clentSocket);

	this->lockObj.lockUp();

	this->clientMap.erase(conn->clentSocket);

	// 是否限制了ip链接最大数量

	if (this->maxConnectionsPerIpNumber > 0)
	{
		std::map<std::string, unsigned int>::iterator itera = this->connectionIp.find(conn->clientIp);

		if (itera != this->connectionIp.end() && (--itera->second) < 0 )
		{	
			this->connectionIp.erase(conn->clientIp);
		}
	}

	this->lockObj.unLock();

	conn->clentSocket = IOCP_NULLKEY;


}

/*释放链接上下文对象*/
void HTTPServer::freeConnectionContext(ConnectDefinition * conn)
{
	if (conn == NULL)
	{
		return;
	}

	if (conn->clentSocket)
	{
		this->iocNetWork.remove(conn->clentSocket);
	}

	if (conn->requestClient)
	{
		conn->requestClient->reset();

		delete conn->requestClient;
	}

	if (conn->responserClient)
	{
		conn->responserClient->reset();

		delete conn->responserClient;
	}

	delete conn;
}

void HTTPServer::doRequestComplete(ConnectDefinition * conn, size_t status)
{
	assert(conn->requestClient);

	assert(conn->responserClient);

	// 先省略日志

	// 是否保持链接

	if (status == RE_SENDCOMPLETE && conn->requestClient->keepAlive())
	{
		assert(conn->responserClient);

		// 重置
		conn->requestClient->reset();

		conn->responserClient->reset();

		delete conn->responserClient;

		conn->responserClient = NULL;

		this->iocNetWork.refresh(conn->clentSocket);

		// 开始下一个请求
		int code = conn->requestClient->run(conn, conn->clentSocket, this->recvTimeout());

		if (code != RE_SUCCESS)
		{
			this->doConnectionClosed(conn, code);
		}

	}
	else
	{
		this->doConnectionClosed(conn, status);
	}

}

